﻿using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading;

using LukeSkywalker.IPNetwork;

using System.Net.DnsToolkit.SocketAsync;
using System.Net.DnsToolkit.Packet;
using System.Net.DnsToolkit.Serializer;
using Ninject;
using System.Net.DnsToolkit.Configuration;
using System.Net.DnsToolkit.Packet.Busy;

namespace System.Net.DnsToolkit.Scanner {
    public class RootScanner {


        static readonly IMessage RootQuery = new Message {
            Header = new Header {
                Id = 0x1337,
                OpCode = OpCodeEnum.StandardQuery,
                QueryType = QREnum.QUERY,
                QuestionCount = 1,
                RecursionDesired = true
            },
            Questions = new List<IQuestion> {
                new Question {
                    QName = new DomainName { Name = "." },
                    QClass = QClassEnum.IN,
                    QType = QTypeEnum.NS
                }
            }
        };

        static readonly byte[] RootQueryBs;

        public class Ticket {

            private readonly IPAddress _ipaddress;
            public int I { get; set;  }
            public IPAddress IPAddress {
                get {
                    return _ipaddress;
                }
            }

            public Ticket(IPAddress ipaddress, int i) {
                _ipaddress = ipaddress;
                I = i;
            }
        }

        static bool run = true;
        static readonly object locking = new object();
        static int port = 53;
        static int maxConnection = 40000;
        static int bufferSize = 512;
        static TimeSpan timeout = TimeSpan.FromSeconds(5);
        static bool useRamdomSrcPort = false;
        static readonly UdpClientAsync uca = new UdpClientAsync(maxConnection, bufferSize, timeout, useRamdomSrcPort);


        static RootScanner() {


            IKernel kernel = new StandardKernel(new DnsWriterStreamModule());
            var _writer = kernel.Get<IMessageWriter<System.IO.Stream>>();

            var mem = new MemoryStream();
            _writer.Write(mem, RootQuery);
            RootQueryBs = mem.ToArray();

            
            uca.OnSendSubmitted += UdpClient_OnSendSubmitted;
            uca.OnSendCompleted += UdpClient_OnSendCompleted;
            uca.OnReceiveCompleted += UdpClient_OnReceiveCompleted;


        }


        public static void _Main(string[] argv) {



            int i = 0;

            var statsThread = new Thread(StatsLoop);
            var saveThread = new Thread(SaveLoop);
            
            statsThread.Start();
            saveThread.Start();

            uint lastScan = GetLastScan();

            for (uint j = lastScan; j < uint.MaxValue; j++ )
            {
                IPAddress address = IPNetwork.ToIPAddress(j);
                if (IPNetworkFilter.IsFiltered(address)) {
                    continue;
                }

                i++;
                if (i % 100000 == 0) {
                    SaveScan(address);
                }


                var remote = new IPEndPoint(address, port);
                var ticket = new Ticket(address, i);
                // Console.WriteLine(">{0} {1}", ticket.I, ticket.IPAddress);
                uca.SendTo(remote, RootQueryBs, 0, RootQueryBs.Length, ticket);

                BandwidthScheduler.Schedule();
                
            }

            Thread.Sleep(5000);
        
        }
        
        private static void SaveScan(IPAddress address) {
            uint? longIp;
            IPNetwork.TryToUint(address, out longIp);

            var scan = new Scan {
                Host = address.ToString(), 
                HostLong = (int) longIp, 
                LastScan = DateTime.Now
            };

            using (var db = new RootScannerDataContext(ConnString)) {
                //db.Log = Console.Out;
                db.Scans.InsertOnSubmit(scan);
                db.SubmitChanges();
            }



        }

        private static uint GetLastScan() {

            using (var db = new RootScannerDataContext(ConnString)) {
                Scan scan = db.GetLastScan().SingleOrDefault();
                if (scan != null) {
                    return (uint)scan.HostLong;
                }
            }

            return 0;
        }

        static void UdpClient_OnSendSubmitted(object sender, UdpClientEventArgs e) {
            lock (locking) {

                //var ticket = e.Ticket as Ticket;
                //Console.WriteLine("-{0} {1} {2}", ticket.I, ticket.IPAddress, e.HasException);


                if (e.HasException) {
                    //Console.WriteLine(e.Exception.Message);
                    e.Treated = true;
                    return;
                }

            }

        }


        static void UdpClient_OnSendCompleted(object sender, UdpClientEventArgs e) {
            lock (locking) {
                
                // var ticket = e.Ticket as Ticket;
                //Console.WriteLine("-{0} {1} {2}", ticket.I, ticket.IPAddress, e.HasException);


                if (e.HasException) {
                    //Console.WriteLine(e.Exception.Message);
                    e.Treated = true;
                    return;
                }

            }
        }

        const string ConnString = @"Data Source=.\SQLEXPRESS;AttachDbFilename=c:\vs2008\DarkDns\solution\Scanner\Data\RootScanner.mdf;Integrated Security=True;User Instance=True";

        static readonly List<Host> HostList = new List<Host>();
        static void UdpClient_OnReceiveCompleted(object sender, UdpClientEventArgs e) {
            lock (locking) {
                
                var ticket = e.Ticket as Ticket;
                if (ticket == null) {
                    throw new ApplicationException("ticket");
                }
                //Console.WriteLine("*{0} {1} {2}", ticket.I, ticket.IPAddress, e.HasException);

                if (e.HasException) {
                    //Console.WriteLine(e.Exception.Message);
                    e.Treated = true;
                    return;
                }

                try {
                    var mem = new MemoryStream(e.Buffer, e.Offset, e.Length);
                    //Message response = null;
                    //DnsReader.Read(mem, out response);

                    uint? longIp;
                    IPNetwork.TryToUint(ticket.IPAddress, out longIp);

                    var host = new Host {
                        IpAddress = ticket.IPAddress.ToString(),
                        IpLong = (int) longIp,
                        LastSeen = DateTime.Now,
                        Data = new Data.Linq.Binary(mem.ToArray())
                    };
                    //host.Recursive = response.Header.RecursionAvailable;
                    //host.Opened = response.Header.ResponseCode == RCodeEnum.NoError;
                    lock (HostList) {
                        HostList.Add(host);
                    }

                    //Console.WriteLine("Output: {0}", response.Header.ResponseCode.ToString());
                    //Console.WriteLine("Output: {0}", response.Header.AnswerCount.ToString());
                }
                catch (Exception ex) {
                    Console.WriteLine("Ex: {0}", ex.Message);
                }

            }


        }

        private static void SaveLoop() {

            while (run) {

                lock (locking) {
                    Console.WriteLine("Saving scanned hostlist({0})", HostList.Count);
                }

                using (var db = new RootScannerDataContext(ConnString)) {

                    lock (HostList) {
                        foreach (Host host in HostList) {
                            db.Hosts.InsertOnSubmit(host);
                        }
                        HostList.Clear();
                    }
                    db.SubmitChanges();
                }

                Thread.Sleep(5000);

            }
        }


        private static void StatsLoop() {

            while (run) {

                lock (locking) {

                    Console.WriteLine();
                    Console.WriteLine("Saea in pool: {0}", uca.SocketPoolCount);
                    Console.WriteLine("Working saea: {0}", uca.MaxConnection - uca.SocketPoolCount);
                    Console.WriteLine("Total sent: {0}", uca.TotalSendTo);
                    Console.WriteLine("Total received: {0}", uca.TotalReceiveFrom);
                    Console.WriteLine("Total receive timeout: {0}", uca.TotalReceiveTimeout);
                    Console.WriteLine("AdressAlreadyInUse: {0}", uca.AdressAlreadyInUse);
                    Console.WriteLine("PortCollision: {0}", uca.PortCollision);
                    Console.WriteLine("Sleep: {0}", BandwidthScheduler.GetSleepTime());

                    using (var db = new RootScannerDataContext(ConnString)) {
                        //db.Log = Console.Out;
                        int scanInterval = db.Get10000ScanInterval();
                        int hostCount = db.GetHostCount();
                        string lastScan = db.GetLastScan().SingleOrDefault().Host;

                        Console.WriteLine("Scan interval: {0}", scanInterval);
                        Console.WriteLine("Host count: {0}", hostCount);
                        Console.WriteLine("Last Scan: {0}", lastScan);

                    }


                    foreach (byte lockedPort in uca.LockedPorts) {
                        Console.WriteLine("Port in use : {0}", lockedPort);
                    }
                }
                Thread.Sleep(10000);
            }

        }


    }
}
